/****************************************************************************
** Licensed Materials - Property of IBM
**
** Governed under the terms of the International
** License Agreement for Non-Warranted Sample Code.
**
** (C) COPYRIGHT International Business Machines Corp. 1995 - 2003
** All Rights Reserved.
**
** US Government Users Restricted Rights - Use, duplication or
** disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*****************************************************************************
**
** SOURCE FILE NAME: utilrecov.C
**
** SAMPLE: Utilities for the backup, restore and log file samples
**
**         This set of utilities gets the server working path, prunes
**         recovery history file, creates a database, backup a database,
**         drop a database, saves and restores log retain values, displays
**         the log buffer files.
**
** DB2 APIs USED:
**         db2CfgGet -- Get Configuration
**         db2CfgSet -- Set Configuration
**         db2Prune -- Prune Recovery History File
**         db2Backup -- Backup Database
**         sqledrpd -- Drop and uncatalog a database
**         sqlecrea -- creates a database
**
** STRUCTURES USED:
**         sqlca
**         sqledbdesc
**         db2PruneStruct
**         db2HistoryData
*****************************************************************************
**
** For more information on the sample programs, see the README file.
**
** For information on developing C++ applications, see the Application
** Development Guide.
**
** For information on DB2 APIs, see the Administrative API Reference.
**
** For the latest information on programming, building, and running DB2
** applications, visit the DB2 application development website:
**     http://www.software.ibm.com/data/db2/udb/ad
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <sqlenv.h>
#include <sqlutil.h>
#include <sqlca.h>
#include <db2ApiDf.h>
#include <string.h>
#include <ctype.h>
#include <sqludf.h>
#include "utilemb.h"
#if ((__cplusplus >= 199711L) && !defined DB2HP && !defined DB2AIX) || \
    (DB2LINUX && (__LP64__ || (__GNUC__ >= 3)) )
   #include <iostream>
   using namespace std;
#else
   #include <iostream.h>
#endif

#define CHECKRC(x,y)                                   \
    if ((x) != 0)                                      \
    {                                                  \
       printf("Non-zero rc from function %s.\n", (y)); \
       return (x);                                     \
    }

#define MIN(x,y)                                       \
    ((x)<(y)?(x):(y))

class UtilRecov
{
  public:
    int ServerWorkingPathGet(DbEmb *, char *);
    int DbLogRetainValueSave(DbEmb *, sqluint16 *);
    int DbLogRetainValueRestore(DbEmb *, sqluint16 *);
    int DbRecoveryHistoryFilePrune(DbEmb *);
    int DbBackup(DbEmb *, char *, db2BackupStruct *);
    int DbCreate(char *, char *);
    int DbDrop(char *);
};

class UtilLog
{
  public:
    int LogBufferDisplay(char *, sqluint32);
    int LogRecordDisplay(char *, sqluint32, sqluint16, sqluint16);
    int SimpleLogRecordDisplay(sqluint16, sqluint16, char *, sqluint32);
    int ComplexLogRecordDisplay(sqluint16, sqluint16, char *, sqluint32,
                                sqluint8, char *, sqluint32);
    int LogSubRecordDisplay(char *, sqluint16);
    int UserDataDisplay(char *, sqluint16);
};

class RID
{
  private:
    char ridParts[6];
    char ridString[14];

    void toString();

  public:
    int size() { return 6; };
    void set(char * buf );
    char *getString();
};

void RID::toString()
{
    char *ptrBuf = this->ridParts;

    sprintf( ridString, "x%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X",
             *ptrBuf, *(ptrBuf+1), *(ptrBuf+2),
             *(ptrBuf+3), *(ptrBuf+4), *(ptrBuf+5) );
}

void RID::set( char *buf )
{
    strncpy( this->ridParts, buf, this->size() );
}

char* RID::getString()
{
    this->toString();
    return ridString;
}

//**************************************************************************
// ServerWorkingPathGet
// Get the server working directory path where the backup images are kept
//**************************************************************************
int UtilRecov::ServerWorkingPathGet( DbEmb *db,
                                     char serverWorkingPath[] )
{
  int          rc = 0;
  struct sqlca sqlca = { 0 };
  char         serverLogPath[SQL_PATH_SZ + 1] = { 0 };
  db2CfgParam  cfgParameters[1] = { 0 };
  db2Cfg       cfgStruct = { 0 };
  int          len = 0;

  // initialize cfgParameters
  cfgParameters[0].flags = 0;
  cfgParameters[0].token = SQLF_DBTN_LOGPATH;
  cfgParameters[0].ptrvalue = new char[SQL_PATH_SZ + 1];

  // initialize cfgStruct
  cfgStruct.numItems = 1;
  cfgStruct.paramArray = cfgParameters;
  cfgStruct.flags = db2CfgDatabase;
  cfgStruct.dbname = db->getAlias();

  cout << "\nUSE THE DB2 API:\n" << endl;
  cout << "  db2CfgGet -- GET CONFIGURATION\n";
  cout << "TO GET THE DATABASE CONFIGURATION AND DETERMINE\n";
  cout << "THE SERVER WORKING PATH.\n" << endl;

  // get database configuration
  db2CfgGet( db2Version900, (void *)&cfgStruct, &sqlca );
  DB2_API_CHECK("server log path -- get");

  strncpy( serverLogPath, cfgParameters[0].ptrvalue, SQL_PATH_SZ );
  delete [] cfgParameters[0].ptrvalue;

  // choose server working path
  // Let's say serverLogPath = "C:\DB2\NODE0001\....".
  // Keep for serverWorkingPath "C:\DB2" only.
  len = (int)(strstr(serverLogPath, "NODE") - serverLogPath - 1);
  memcpy( serverWorkingPath, serverLogPath, len );
  serverWorkingPath[len] = '\0';

  return 0;
} // UtilRecov::ServerWorkingPathGet

//**************************************************************************
// DbLogRetainValueSave
// Save LOGRETAIN value for the database
//**************************************************************************
int UtilRecov::DbLogRetainValueSave( DbEmb     *db,
                                     sqluint16 *pLogRetainValue )
{
  int          rc = 0;
  struct sqlca sqlca =  { 0 };
  db2CfgParam  cfgParameters[1] = { 0 };
  db2Cfg       cfgStruct = { 0 };

  // save log retain value
  cout << "\n******* Save LOGRETAIN for '" << db->getAlias()
    << "' database. *******" << endl;
  cfgParameters[0].flags = 0;
  cfgParameters[0].token = SQLF_DBTN_LOG_RETAIN;
  cfgParameters[0].ptrvalue = (char *)pLogRetainValue;

  // initialize cfgStruct
  cfgStruct.numItems = 1;
  cfgStruct.paramArray = cfgParameters;
  cfgStruct.flags = db2CfgDatabase;
  cfgStruct.dbname = db->getAlias();

  // get database configuration
  db2CfgGet(db2Version900, (void *)&cfgStruct, &sqlca);
  DB2_API_CHECK("log retain value -- save");

  return 0;
} // UtilRecov::DbLogRetainValueSave

//**************************************************************************
// DbLogRetainValueRestore
// Restore the LOGRETAIN value for the database
//**************************************************************************
int UtilRecov::DbLogRetainValueRestore( DbEmb     *db,
                                        sqluint16 *pLogRetainValue )
{
  int          rc = 0;
  struct sqlca sqlca = { 0 };
  db2CfgParam  cfgParameters[1] = { 0 };
  db2Cfg       cfgStruct = { 0 };

  // restore the log retain value
  cout << "\n***** Restore LOGRETAIN for '" << db->getAlias()
    << "' database. *****" << endl;
  cfgParameters[0].flags = 0;
  cfgParameters[0].token = SQLF_DBTN_LOG_RETAIN;
  cfgParameters[0].ptrvalue = (char *)pLogRetainValue;

  // initialize cfgStruct
  cfgStruct.numItems = 1;
  cfgStruct.paramArray = cfgParameters;
  cfgStruct.flags = db2CfgDatabase | db2CfgDelayed;
  cfgStruct.dbname = db->getAlias();

  // set database configuration
  db2CfgSet(db2Version900, (void *)&cfgStruct, &sqlca);
  DB2_API_CHECK("log retain value -- restore");

  return 0;
} // UtilRecov::DbLogRetainValueRestore

//**************************************************************************
// DbRecoveryHistoryFilePrune
// Prunes the recovery history file by calling db2Prune API
//**************************************************************************
int UtilRecov::DbRecoveryHistoryFilePrune( DbEmb *db )
{
  int                   rc = 0;
  struct sqlca          sqlca = { 0 };
  struct db2PruneStruct histPruneParam = { 0 };
  char                  timeStampPart[SQLU_TIME_STAMP_LEN + 1] = { 0 };

  cout << "\n***************************************\n";
  cout << "*** PRUNE THE RECOVERY HISTORY FILE ***\n";
  cout << "***************************************\n";
  cout << "\n-----------------------------------------------------------";
  cout << "\nUSE THE DB2 API:" << endl;
  cout << "  db2Prune -- PRUNE RECOVERY HISTORY FILE" << endl;
  cout << "AND THE SQL STATEMENTS:" << endl;
  cout << "  CONNECT" << endl;
  cout << "  CONNECT RESET" << endl;
  cout << "TO PRUNE THE RECOVERY HISTORY FILE." << endl;

  // connect to the database
  rc = db->Connect();
  CHECKRC(rc, "db->Connect");

  // prune the recovery history file
  cout << "\n  Prune the recovery history file for '" << db->getAlias()
    << "' database." << endl;
  histPruneParam.piString = timeStampPart;
  strcpy(timeStampPart, "2010");        // year 2010
  histPruneParam.iAction = DB2PRUNE_ACTION_HISTORY;
  histPruneParam.iOptions = DB2PRUNE_OPTION_FORCE;

  // Prune Recovery History File
  db2Prune(db2Version900, &histPruneParam, &sqlca);
  DB2_API_CHECK("recovery history file -- prune");

  // disconnect from the database
  rc = db->Disconnect();
  CHECKRC(rc, "db->Disconnect");

  return 0;
} // UtilRecov::DbRecoveryHistoryFilePrune

//**************************************************************************
// DbBackup
// Performs the database backup
//**************************************************************************
int UtilRecov::DbBackup( DbEmb           *db,
                         char            serverWorkingPath[],
                         db2BackupStruct *backupStruct)

{
  struct sqlca        sqlca = { 0 };
  db2TablespaceStruct tablespaceStruct = { 0 };
  db2MediaListStruct  mediaListStruct = { 0 };

  //******************************
  //    BACK UP THE DATABASE
  //******************************
  cout << "\n  Backing up the \'" << db->getAlias() << "\' database...\n";

  tablespaceStruct.tablespaces = NULL;
  tablespaceStruct.numTablespaces = 0;

  mediaListStruct.locations = &serverWorkingPath;
  mediaListStruct.numLocations = 1;
  mediaListStruct.locationType = SQLU_LOCAL_MEDIA;

  backupStruct->piDBAlias = db->getAlias();
  backupStruct->piTablespaceList = &tablespaceStruct;
  backupStruct->piMediaList = &mediaListStruct;
  backupStruct->piUsername = db->getUser();
  backupStruct->piPassword = db->getPswd();
  backupStruct->piVendorOptions = NULL;
  backupStruct->iVendorOptionsSize = 0;
  backupStruct->iCallerAction = DB2BACKUP_BACKUP;
  backupStruct->iBufferSize = 16;        /*  16 x 4KB */
  backupStruct->iNumBuffers = 2;
  backupStruct->iParallelism = 1;
  backupStruct->iOptions = DB2BACKUP_OFFLINE | DB2BACKUP_DB;

  // The API db2Backup creates a backup copy of a database.
  // This API automatically establishes a connection to the specified
  // database. (This API can also be used to create a backup copy of a
  //  table space).
  db2Backup(db2Version900, backupStruct, &sqlca);
  DB2_API_CHECK("Database -- Backup");

  while (sqlca.sqlcode != 0)
  {
    // continue the backup operation

    // depending on the sqlca.sqlcode value, user action may be
    // required, such as mounting a new tape

    cout << "\n  Continuing the backup process..." << endl;

    backupStruct->iCallerAction = DB2BACKUP_CONTINUE;

    db2Backup(db2Version900, backupStruct, &sqlca);

    DB2_API_CHECK("Database -- Backup");
  }

  cout << "  Backup finished." << endl;
  cout << "    - backup image size     : "
    << backupStruct->oBackupSize << " MB" << endl;
  cout << "    - backup image path     : "
    << mediaListStruct.locations[0] << endl;
  cout << "    - backup image timestamp: "
    << backupStruct->oTimestamp << endl;

  return 0;
} // UtilRecov::DbBackup

//**************************************************************************
// DbCreate
// Create the specified database
//**************************************************************************
int UtilRecov::DbCreate( char existingDbAlias[],
                         char newDbAlias[] )
{
  struct sqlca        sqlca = { 0 };
  char                dbName[SQL_DBNAME_SZ + 1] = { 0 };
  char                dbLocalAlias[SQL_ALIAS_SZ + 1] = { 0 };
  char                dbPath[SQL_PATH_SZ + 1] = { 0 };
  struct sqledbdesc   dbDescriptor = { 0 };
  SQLEDBTERRITORYINFO territoryInfo = { 0 };
  struct db2CfgParam  cfgParameters[2] = { 0 };
  struct db2Cfg       cfgStruct = { 0 };

  cout << "\n  Create '" << newDbAlias
    << "' empty db. with the same codeset as '" << existingDbAlias
    << "' db." << endl;

  // initialize cfgParameters
  cfgParameters[0].flags = 0;
  cfgParameters[0].token = SQLF_DBTN_TERRITORY;
  cfgParameters[0].ptrvalue = new char[SQL_LOCALE_SIZE + 1];
  memset(cfgParameters[0].ptrvalue, '\0', SQL_LOCALE_SIZE + 1);
  cfgParameters[1].flags = 0;
  cfgParameters[1].token = SQLF_DBTN_CODESET;
  cfgParameters[1].ptrvalue = new char[SQL_CODESET_SIZE + 1];
  memset(cfgParameters[1].ptrvalue, '\0', SQL_CODESET_SIZE + 1);

  // initialize cfgStruct
  cfgStruct.numItems = 2;
  cfgStruct.paramArray = cfgParameters;
  cfgStruct.flags = db2CfgDatabase;
  cfgStruct.dbname = existingDbAlias;

  // get two database configuration parameters
  db2CfgGet(db2Version900, (void *)&cfgStruct, &sqlca);
  DB2_API_CHECK("DB Config. -- Get");

  // create a new database
  strcpy(dbName, newDbAlias);
  strcpy(dbLocalAlias, newDbAlias);
  strcpy(dbPath, "");

  strcpy(dbDescriptor.sqldbdid, SQLE_DBDESC_2);
  dbDescriptor.sqldbccp = 0;
  dbDescriptor.sqldbcss = SQL_CS_NONE;

  strcpy(dbDescriptor.sqldbcmt, "");
  dbDescriptor.sqldbsgp = 0;
  dbDescriptor.sqldbnsg = 10;
  dbDescriptor.sqltsext = -1;
  dbDescriptor.sqlcatts = NULL;
  dbDescriptor.sqlusrts = NULL;
  dbDescriptor.sqltmpts = NULL;

  strcpy(territoryInfo.sqldbcodeset, (char *)cfgParameters[1].ptrvalue);
  strcpy(territoryInfo.sqldblocale, (char *)cfgParameters[0].ptrvalue);

  // create database
  sqlecrea(dbName,
           dbLocalAlias,
           dbPath, &dbDescriptor, &territoryInfo, '\0', NULL, &sqlca);

  DB2_API_CHECK("Database -- Create");

  // release the allocated memory
  delete [] cfgParameters[0].ptrvalue;
  delete [] cfgParameters[1].ptrvalue;

  return 0;
} // UtilRecov::DbCreate

//**************************************************************************
// DbDrop
// Drops and uncatalogs the specified database alias
//**************************************************************************
int UtilRecov::DbDrop( char dbAlias[] )
{
  struct sqlca sqlca = { 0 };

  cout << "\n  Drop the '" << dbAlias << "' database." << endl;

  // drop and uncatalog the database
  sqledrpd(dbAlias, &sqlca);
  DB2_API_CHECK("Database -- Drop");

  return 0;
} // UtilRecov::DbDrop

//*************************************************************************
// LogBufferDisplay
// Displays the log buffer
//*************************************************************************
int UtilLog::LogBufferDisplay( char      *logBuffer,
                               sqluint32 numLogRecords )
{
  int       rc = 0;
  sqluint32 logRecordNb = 0;
  sqluint32 recordSize = 0;
  sqluint16 recordType = 0;
  sqluint16 recordFlag = 0;
  char      *recordBuffer = NULL;

  // initialize the recordBuffer
  recordBuffer = logBuffer + sizeof(SQLU_LSN);

  for (logRecordNb = 0; logRecordNb < numLogRecords; logRecordNb++)
  {
    recordSize = *(sqluint32 *) (recordBuffer);
    recordType = *(sqluint16 *) (recordBuffer + 4);
    recordFlag = *(sqluint16 *) (recordBuffer + 6);

    rc = LogRecordDisplay(recordBuffer, recordSize, recordType, recordFlag);
    CHECKRC(rc, "LogRecordDisplay");

    // update the recordBuffer
    recordBuffer = recordBuffer + recordSize + sizeof(SQLU_LSN);
  }

  return 0;
} // UtilLog::LogBufferDisplay

//**************************************************************************
// LogRecordDisplay
// Displays the log records
//**************************************************************************
int UtilLog::LogRecordDisplay( char      *recordBuffer,
                               sqluint32 recordSize,
                               sqluint16 recordType,
                               sqluint16 recordFlag )
{
  int       rc = 0;
  sqluint32 logManagerLogRecordHeaderSize = 0;
  char      *recordDataBuffer = NULL;
  sqluint32 recordDataSize = 0;
  char      *recordHeaderBuffer = NULL;
  sqluint8  componentIdentifier = 0;
  sqluint32 recordHeaderSize = 0;

  // determine the logManagerLogRecordHeaderSize
  logManagerLogRecordHeaderSize = 20;
  if( recordType == 0x0043 )  // compensation
  {
    logManagerLogRecordHeaderSize += sizeof(SQLU_LSN);
    if( recordFlag & 0x0002 )    // propagatable
    {
      logManagerLogRecordHeaderSize += sizeof(SQLU_LSN);
    }
  }

  switch (recordType)
  {
    case 0x008A:                // Local Pending List
    case 0x0084:                // Normal Commit
    case 0x0041:                // Normal Abort
      recordDataBuffer = recordBuffer + logManagerLogRecordHeaderSize;
      recordDataSize = recordSize - logManagerLogRecordHeaderSize;
      rc = SimpleLogRecordDisplay( recordType,
                                   recordFlag,
                                   recordDataBuffer,
                                   recordDataSize );
      CHECKRC(rc, "SimpleLogRecordDisplay");
      break;
    case 0x004E:                // Normal
    case 0x0043:                // Compensation
      recordHeaderBuffer = recordBuffer + logManagerLogRecordHeaderSize;
      componentIdentifier = *(sqluint8 *) recordHeaderBuffer;
      switch (componentIdentifier)
      {
        case 1:                 // Data Manager Log Record
          recordHeaderSize = 6;
          break;
        default:
          cout << "    Unknown complex log record: " << recordSize
            << " " << recordType << " " << componentIdentifier << endl;
          return 1;
      }
      recordDataBuffer = recordBuffer +
        logManagerLogRecordHeaderSize + recordHeaderSize;
      recordDataSize = recordSize -
        logManagerLogRecordHeaderSize - recordHeaderSize;
      rc = ComplexLogRecordDisplay( recordType,
                                    recordFlag,
                                    recordHeaderBuffer,
                                    recordHeaderSize,
                                    componentIdentifier,
                                    recordDataBuffer,
                                    recordDataSize );
      CHECKRC(rc, "ComplexLogRecordDisplay");
      break;
    default:
      cout << "    Unknown log record: " << recordSize << " "
        << (char)recordType << endl;
      break;
  }

  return 0;
} // UtilLog::LogRecordDisplay

//**************************************************************************
// SimpleLogRecordDisplay
// Prints the minimum details of the log record
//**************************************************************************
int UtilLog::SimpleLogRecordDisplay( sqluint16 recordType,
                                     sqluint16 recordFlag,
                                     char      *recordDataBuffer,
                                     sqluint32 recordDataSize )
{
  int       rc = 0;
  sqluint32 timeTransactionCommited = 0;
  sqluint16 authIdLen = 0;
  char      *authId = NULL;

  switch (recordType)
  {
    case 138:
      cout << "\n    Record type: Local pending list" << endl;
      timeTransactionCommited = *(sqluint32 *) (recordDataBuffer);
      authIdLen = *(sqluint16 *) (recordDataBuffer + 2*sizeof(sqluint32));
      authId = (char *)malloc(authIdLen + 1);
      memset(authId, '\0', (authIdLen + 1 ));
      memcpy(authId, (char *)(recordDataBuffer + 2*sizeof(sqluint32) +
                              sizeof(sqluint16)), authIdLen);
      authId[authIdLen] = '\0';
      cout << "      UTC transaction committed(in secs since 70-01-01)" << ": "
        << dec << timeTransactionCommited << endl;
      cout << "      authorization ID of the application: " << authId << endl;
      break;
    case 132:
      cout << "\n    Record type: Normal commit" << endl;
      timeTransactionCommited = *(sqluint32 *) (recordDataBuffer);
      authIdLen = *(sqluint16 *) (recordDataBuffer + 2*sizeof(sqluint32));
      authId = (char *)malloc(authIdLen + 1);
      memset( authId, '\0', (authIdLen + 1 ));
      memcpy(authId, (char *)(recordDataBuffer + 2*sizeof(sqluint32) +
                              sizeof(sqluint16)), authIdLen);
      authId[authIdLen] = '\0';
      cout << "      UTC transaction committed(in secs since 70-01-01)" << ": "
        << dec << timeTransactionCommited << endl;
      cout << "      authorization ID of the application: " << authId << endl;
      break;
    case 65:
      cout << "\n    Record type: Normal abort" << endl;
      authIdLen = (sqluint16) (recordDataSize);
      authId = (char *)malloc(authIdLen + 1);
      memset( authId, '\0', (authIdLen + 1 ));
      memcpy(authId, (char *)(recordDataBuffer + sizeof(sqluint16)), authIdLen);
      authId[authIdLen] = '\0';
      cout << "      authorization ID of the application: " << authId << endl;
      break;
    default:
      cout << "    Unknown simple log record: "
        << (char)recordType << " " << recordDataSize << endl;
      break;
  }

  return 0;

} // UtilLog::SimpleLogRecordDisplay

//**************************************************************************
// ComplexLogRecordDisplay
// Prints a detailed information of the log record
//**************************************************************************
int UtilLog::ComplexLogRecordDisplay( sqluint16 recordType,
                                      sqluint16 recordFlag,
                                      char      *recordHeaderBuffer,
                                      sqluint32 recordHeaderSize,
                                      sqluint8  componentIdentifier,
                                      char      *recordDataBuffer,
                                      sqluint32 recordDataSize )
{
  int rc = 0;
  sqluint8 functionIdentifier = 0;

  // for insert, delete, undo delete
  RID       recid;
  sqluint16 subRecordLen = 0;
  sqluint16 subRecordOffset = 0;
  char      *subRecordBuffer = NULL;

  // for update
  RID       newRID;
  sqluint16 newSubRecordLen = 0;
  sqluint16 newSubRecordOffset = 0;
  char      *newSubRecordBuffer = NULL;
  RID       oldRID;
  sqluint16 oldSubRecordLen = 0;
  sqluint16 oldSubRecordOffset = 0;
  char      *oldSubRecordBuffer = NULL;

  // for alter table attributes
  sqluint64 alterBitMask = 0;
  sqluint64 alterBitValues = 0;

  switch( recordType )
  {
    case 0x004E:
      cout << "\n    Record type: Normal" << endl;
      break;
    case 0x0043:
      cout << "\n    Record type: Compensation." << endl;
      break;
    default:
      cout << "\n    Unknown complex log record type: " << recordType
        << endl;
      break;
  }

  switch (componentIdentifier)
  {
    case 1:
      cout << "      component ID: DMS log record" << endl;
      break;
    default:
      cout << "      unknown component ID: " << componentIdentifier << endl;
      break;
  }

  functionIdentifier = *(sqluint8 *) (recordHeaderBuffer + 1);
  switch (functionIdentifier)
  {
    case 161:
      cout << "      function ID: Delete Record" << endl;
      subRecordLen = *( (sqluint16 *)( recordDataBuffer + sizeof(sqluint16) ) );
      recid.set( recordDataBuffer + 3 * sizeof(sqluint16) );
      subRecordOffset = *( (sqluint16 *)( recordDataBuffer + 3 * sizeof(sqluint16) +
                           recid.size() ) );
      cout << "        RID: " << dec << recid.getString() << endl;
      cout << "        subrecord length: " << subRecordLen << endl;
      cout << "        subrecord offset: " << subRecordOffset << endl;
      subRecordBuffer = recordDataBuffer + 3 * sizeof(sqluint16) +
                        recid.size() + sizeof(sqluint16);
      rc = LogSubRecordDisplay( subRecordBuffer, subRecordLen );
      CHECKRC(rc, "LogSubRecordDisplay");
      break;
    case 111:
      cout << "      function ID: Undo Delete Record" << endl;
      subRecordLen = *( (sqluint16 *)( recordDataBuffer + sizeof(sqluint16) ) );
      recid.set( recordDataBuffer + 3 * sizeof(sqluint16) );
      subRecordOffset = *( (sqluint16 *)( recordDataBuffer + 3 * sizeof(sqluint16) +
                                          recid.size() ) );
      cout << "        RID: " << dec << recid.getString() << endl;
      cout << "        subrecord length: " << subRecordLen << endl;
      cout << "        subrecord offset: " << subRecordOffset << endl;
      subRecordBuffer = recordDataBuffer + 3 * sizeof(sqluint16) +
                        recid.size() + sizeof(sqluint16);
      rc = LogSubRecordDisplay(subRecordBuffer, subRecordLen);
      CHECKRC(rc, "LogSubRecordDisplay");
      break;
    case 162:
      cout << "      function ID: Insert Record" << endl;
      subRecordLen = *( (sqluint16 *)( recordDataBuffer + sizeof(sqluint16) ) );
      recid.set( recordDataBuffer + 3 * sizeof(sqluint16) );
      subRecordOffset = *( (sqluint16 *)( recordDataBuffer + 3 * sizeof(sqluint16) +
                                          recid.size() ) );
      cout << "        RID: " << dec << recid.getString() << endl;
      cout << "        subrecord length: " << subRecordLen << endl;
      cout << "        subrecord offset: " << subRecordOffset << endl;
      subRecordBuffer = recordDataBuffer + 3 * sizeof(sqluint16) + recid.size() +
                        sizeof(sqluint16);
      rc = LogSubRecordDisplay( subRecordBuffer, subRecordLen );
      CHECKRC(rc, "LogSubRecordDisplay");
      break;
    case 163:
      cout << "      function ID: Update Record" << endl;
      oldSubRecordLen = *( (sqluint16 *)( recordDataBuffer + sizeof(sqluint16) ) );
      oldRID.set( recordDataBuffer + 3 * sizeof(sqluint16) );
      oldSubRecordOffset = *( (sqluint16 *)( recordDataBuffer + 3 * sizeof(sqluint16) +
                               oldRID.size() ) );
      newSubRecordLen = *( (sqluint16 *)( recordDataBuffer  +
                                          sizeof(sqluint16) +
                                          oldRID.size()     +
                                          sizeof(sqluint32) +
                                          sizeof(sqluint16) +
                                          oldSubRecordLen   +
                                          recordHeaderSize  +
                                          sizeof(sqluint16) ) );
      newRID.set( recordDataBuffer + 3 * sizeof(sqluint16) +
                  oldRID.size() + sizeof(sqluint16) + oldSubRecordLen +
                  recordHeaderSize + sizeof(sqluint16) );

      newSubRecordOffset = *(sqluint16 *)( recordDataBuffer      +
                                           3 * sizeof(sqluint16) +
                                           oldRID.size()         +
                                           sizeof(sqluint16)     +
                                           oldSubRecordLen       +
                                           recordHeaderSize      +
                                           newRID.size()         +
                                           sizeof(sqluint16) );
      cout << "        oldRID: " << dec << oldRID.getString() << endl;
      cout << "        old subrecord length: " << oldSubRecordLen << endl;
      cout << "        old subrecord offset: " << oldSubRecordOffset << endl;
      oldSubRecordBuffer = recordDataBuffer + 3 * sizeof(sqluint16) +
                           oldRID.size() + sizeof(sqluint16);
      rc = LogSubRecordDisplay( oldSubRecordBuffer, oldSubRecordLen );
      CHECKRC(rc, "LogSubRecordDisplay");
      cout << "        newRID: " << dec << newRID.getString() << endl;
      cout << "        new subrecord length: " << newSubRecordLen << endl;
      cout << "        new subrecord offset: " << newSubRecordOffset << endl;
      newSubRecordBuffer = recordDataBuffer      +
                           3 * sizeof(sqluint16) +
                           oldRID.size()         +
                           sizeof(sqluint16)     +
                           oldSubRecordLen       +
                           recordHeaderSize      +
                           3 * sizeof(sqluint16) +
                           newRID.size()         +
                           sizeof(sqluint16) ;
      rc = LogSubRecordDisplay( newSubRecordBuffer, newSubRecordLen );
      CHECKRC(rc, "LogSubRecordDisplay");
      break;
    case 124:
      cout << "      function ID:  Alter Table Attribute" << endl;
      alterBitMask = *(sqluint64 *) (recordDataBuffer);
      alterBitValues = *( (sqluint64 *)(recordDataBuffer + sizeof(sqluint64) ) );
      if( alterBitMask & 0x00000001 )
      {
        // Propagation attribute altered
        cout << "        Propagation attribute is changed to ";
        if (alterBitValues & 0x00000001)
        {
          cout << "ON" << endl;
        }
        else
        {
          cout << "OFF" << endl;
        }
      }
      if (alterBitMask & 0x00000002)
      {
        // Check Pending attribute altered
        cout << "        Check Pending attr. changed to: ";
        if (alterBitValues & 0x00000002)
        {
          cout << "ON" << endl;
        }
        else
        {
          cout << "OFF" << endl;
        }
      }
      if (alterBitMask & 0x00010000)
      {
        // Append Mode attribute altered
        cout << "        Append Mode attr. changed to: ";
        if (alterBitValues & 0x00010000)
        {
          cout << "ON" << endl;
        }
        else
        {
          cout << "OFF" << endl;
        }
      }
      if (alterBitMask & 0x00200000)
      {
        // LF Propagation attribute altered
        cout << "        LF Propagation attribute is changed to ";
        if (alterBitValues & 0x00200000)
        {
          cout << "ON" << endl;
        }
        else
        {
          cout << "OFF" << endl;
        }
      }
      if (alterBitMask & 0x00400000)
      {
        // LOB Propagation attribute altered
        cout << "        LOB Propagation attr.changed to: ";
        if (alterBitValues & 0x00400000)
        {
          cout << "ON" << endl;
        }
        else
        {
          cout << "OFF" << endl;
        }
      }
      break;
    default:
      cout << "      unknown function identifier: "
        << functionIdentifier << endl;
      break;
  }

  return 0;
} // UtilLog::ComplexLogRecordDisplay

/***************************************************************************/
/* LogSubRecordDisplay                                                     */
/* Prints the sub records for the log                                      */
/***************************************************************************/
int UtilLog::LogSubRecordDisplay( char      *recordBuffer,
                                  sqluint16 recordSize )
{
  int       rc = 0;
  sqluint8  recordType = 0;
  sqluint8  updatableRecordType = 0;
  sqluint16 userDataFixedLength = 0;
  char      *userDataBuffer = NULL;
  sqluint16 userDataSize = 0;

  recordType = *(sqluint8 *) (recordBuffer);
  if ((recordType != 0) && (recordType != 4) && (recordType != 16))
  {
    cout << "        Unknown subrecord type." << endl;
  }
  else if (recordType == 4)
  {
    cout << "        subrecord type: Special control" << endl;
  }
  else
    // recordType == 0 or recordType == 16
    // record Type 0 indicates a normal record
    // record Type 16, for the purposes of this program, should be treated
    // as type 0
  {
    cout << "        subrecord type: Updatable, ";
    updatableRecordType = *(sqluint8 *) (recordBuffer + 4);
    if (updatableRecordType != 1)
    {
      cout << "Internal control" << endl;
    }
    else
    {
      cout << "Formatted user data" << endl;
      userDataFixedLength = *(sqluint16 *) (recordBuffer + 6);
      cout << "        user data fixed length: "
        << dec << userDataFixedLength << endl;
      userDataBuffer = recordBuffer + 8;
      userDataSize = recordSize - 8;
      rc = UserDataDisplay(userDataBuffer, userDataSize);
      CHECKRC(rc, "UserDataDisplay");
    }
  }
  return 0;
} // UtilLog::LogSubRecordDisplay

//**************************************************************************
// UserDataDisplay
// Displays the user data section
//**************************************************************************
int UtilLog::UserDataDisplay( char      *dataBuffer,
                              sqluint16 dataSize )
{
  int       rc = 0;
  sqluint16 line = 0;
  sqluint16 col = 0;
  const int rowLength = 10;

  cout << "        user data:" << endl;

  for (line = 0; line * rowLength < dataSize; line = line + 1)
  {
    cout << "        ";
    for (col = 0; col < rowLength; col = col + 1)
    {
      if (line * rowLength + col < dataSize)
      {
        cout.fill('0');
        cout.width(2);
        cout.setf(ios::uppercase);
        cout << hex << (int)(dataBuffer[line * rowLength + col] & 0x0ff) <<
          " ";
      }
      else
      {
        cout << "   ";
      }
    }
    cout << "*";
    for (col = 0; col < rowLength; col = col + 1)
    {
      if (line * rowLength + col < dataSize)
      {
        if (isalpha(dataBuffer[line * rowLength + col]) ||
            isdigit(dataBuffer[line * rowLength + col]))
        {
          cout << dataBuffer[line * rowLength + col];
        }
        else
        {
          cout << ".";
        }
      }
      else
      {
        cout << " ";
      }
    }
    cout << "*" << endl;
  }
  cout.setf(ios::dec);

  return 0;
} // UtilLog::UserDataDisplay
